import sys
import os
import re
import shutil
import time
import numpy as np
import pandas as pd
from scipy.fftpack import fft
from scipy.signal import firwin, kaiserord
import scipy.signal as signal

from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, 
                             QGridLayout, QGroupBox, QCheckBox, QRadioButton, QComboBox, 
                             QLineEdit, QPushButton, QLabel, QFileDialog, QMessageBox, 
                             QButtonGroup)
from PyQt5.QtCore import Qt
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt

class DAQ_App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.filepaths = []
        self.initUI()

    def initUI(self):
        self.setWindowTitle("LLRF Data Process (PyQt Version)")
        self.setGeometry(100, 100, 480, 800) # Adjusted window size

        # --- Main Layout ---
        main_widget = QWidget()
        self.setCentralWidget(main_widget)
        main_layout = QHBoxLayout(main_widget)

        # --- Controls Panel ---
        controls_panel = QWidget()
        controls_panel.setMaximumWidth(450)
        controls_panel_layout = QVBoxLayout(controls_panel)
        main_layout.addWidget(controls_panel)
        main_layout.addStretch()

        # --- Control Widgets ---
        self.create_filename_group(controls_panel_layout)
        
        controls_layout = QGridLayout()
        self.create_channel_group(controls_layout)
        self.create_mode_group(controls_layout)
        self.create_stability_group(controls_layout)
        self.create_plot_style_group(controls_layout)
        self.create_command_group(controls_layout)
        controls_panel_layout.addLayout(controls_layout)
        
        self.create_parameter_group(controls_panel_layout)
        
        controls_panel_layout.addStretch(1)

        # --- Menu Bar ---
        self.create_menu_bar()

    def create_menu_bar(self):
        menubar = self.menuBar()
        file_menu = menubar.addMenu('&File')

        load_action = file_menu.addAction('&Load File(s)...')
        load_action.setShortcut('Ctrl+O')
        load_action.triggered.connect(self.load_files)
        
        file_menu.addSeparator()
        
        preview_action = file_menu.addAction('&Preview Data')
        preview_action.triggered.connect(self.preview_data)

        file_menu.addSeparator()

        exit_action = file_menu.addAction('&Exit')
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)

        help_menu = menubar.addMenu('&Help')
        about_action = help_menu.addAction('&About')
        about_action.triggered.connect(lambda: QMessageBox.about(self, "About", "DAQ plotting tool converted to PyQt."))


    def create_filename_group(self, parent_layout):
        group = QGroupBox("FILENAME")
        layout = QVBoxLayout()
        self.filename_display = QLineEdit("Display the filename you have selected")
        self.filename_display.setReadOnly(True)
        layout.addWidget(self.filename_display)
        group.setLayout(layout)
        parent_layout.addWidget(group)

    def create_channel_group(self, parent_layout):
        group = QGroupBox("SELECT CH.")
        layout = QGridLayout()
        
        self.channel_checkboxes = []
        self.channel_names = []
        default_names = ['Pt', 'Ref', 'Pf', 'Pr1', 'pf2', 'pr2', 'pt2', 'Backup',
                         'Err', 'dErr', 'PI_FB', 'PI+FF', 'FF1', 'FF2', 'Mod', 'DAC']
        
        for i in range(16):
            row, col = (i, 0) if i < 8 else (i - 8, 2)
            
            checkbox = QCheckBox(f"ADC{i+1}")
            self.channel_checkboxes.append(checkbox)
            layout.addWidget(checkbox, row, col)
            
            name_edit = QLineEdit(default_names[i])
            self.channel_names.append(name_edit)
            layout.addWidget(name_edit, row, col + 1)
        
        group.setLayout(layout)
        parent_layout.addWidget(group, 0, 0)

    def create_mode_group(self, parent_layout):
        group = QGroupBox("SELECT MODE")
        layout = QGridLayout()

        self.plot_mode1_group = self._create_radio_group(["A/P", "I/Q"], layout, 0)
        self.plot_mode2_group = self._create_radio_group(["Wave", "FFT"], layout, 1)
        self.plot_mode3_group = self._create_radio_group(["Plot", "Save Figure"], layout, 2)
        self.plot_mode4_group = self._create_radio_group(["Auto", "Manual"], layout, 3, "Auto")
        self.plot_mode5_group = self._create_radio_group([("Spon", "spurious_on_radio"), ("Spoff", "spurious_off_radio")], layout, 4, "Spoff")

        group.setLayout(layout)
        parent_layout.addWidget(group, 1, 0)

    def _create_radio_group(self, options, layout, row, default=None):
        button_group = QButtonGroup(self)
        for i, option in enumerate(options):
            text, var_name = (option, None) if isinstance(option, str) else option
            radio = QRadioButton(text)
            if var_name:
                setattr(self, var_name, radio)
            button_group.addButton(radio)
            layout.addWidget(radio, row, i)
            if (default and text == default) or (not default and i == 0):
                radio.setChecked(True)
        return button_group

    def create_stability_group(self, parent_layout):
        group = QGroupBox("STABILITIES")
        layout = QHBoxLayout()
        self.da_check = QCheckBox("dA/A")
        self.dp_check = QCheckBox("dP")
        layout.addWidget(self.da_check)
        layout.addWidget(self.dp_check)
        group.setLayout(layout)
        parent_layout.addWidget(group, 2, 0)

    def create_plot_style_group(self, parent_layout):
        group = QGroupBox("PLOT STYLE")
        layout = QGridLayout()
        
        # Font size, Line width, Figsize
        self.font_size_combo = self._add_combo(layout, "Font", 0, ["6", "8", "10", "12", "14", "16"], "10")
        self.linewidth_combo = self._add_combo(layout, "Linewidth", 1, ["0.5", "1", "1.5", "2", "2.5"], "1")
        self.figsize_w_combo = self._add_combo(layout, "Figsize W", 2, [str(i) for i in range(5, 21)], "8")
        self.figsize_h_combo = self._add_combo(layout, "Figsize H", 3, [str(i) for i in range(4, 16)], "6")
        
        # Plot Style (Single/Multi, LogY)
        self.plot_style_group = QButtonGroup(self)
        self.plot_style_single = QRadioButton("Single")
        self.plot_style_multi = QRadioButton("Muti")
        self.plot_style_single.setChecked(True)
        self.plot_style_group.addButton(self.plot_style_single)
        self.plot_style_group.addButton(self.plot_style_multi)
        layout.addWidget(self.plot_style_single, 4, 1)
        layout.addWidget(self.plot_style_multi, 4, 2)
        
        self.plot_style_logy = QCheckBox("LogY")
        layout.addWidget(self.plot_style_logy, 5, 1)

        group.setLayout(layout)
        parent_layout.addWidget(group, 0, 1)

    def _add_combo(self, layout, label, row, items, default):
        layout.addWidget(QLabel(label), row, 0)
        combo = QComboBox()
        combo.addItems(items)
        combo.setCurrentText(default)
        layout.addWidget(combo, row, 1, 1, 2)
        return combo

    def create_command_group(self, parent_layout):
        group = QGroupBox("COMMAND")
        layout = QHBoxLayout()
        plot_button = QPushButton("PLOT DATA")
        plot_button.clicked.connect(self.plot_data)
        layout.addWidget(plot_button)
        group.setLayout(layout)
        parent_layout.addWidget(group, 1, 1)

    def create_parameter_group(self, parent_layout):
        group = QGroupBox("UNIT AND TIME SCALE")
        layout = QGridLayout()
        self.fs_edit = self._add_param(layout, "Fs", 0, "0.1", "[MHz]")
        self.time_unit_combo = self._add_combo(layout, "Time Unit", 1, ["[us]", "[ms]", "[s]", "[samples]"], "[us]")
        self.t_start_edit = self._add_param(layout, "t_start", 2, "0")
        self.t_end_edit = self._add_param(layout, "t_end", 3, "100")
        self.freq_unit_combo = self._add_combo(layout, "Freq Unit", 4, ["[Hz]", "[kHz]", "[MHz]"], "[Hz]")
        self.f_start_edit = self._add_param(layout, "f_start", 5, "0")
        self.f_end_edit = self._add_param(layout, "f_end", 6, "10000")
        self.p_min_edit = self._add_param(layout, "P_min", 7, "", "[deg]")
        self.p_max_edit = self._add_param(layout, "P_max", 8, "", "[deg]")
        self.sel_edit = self._add_param(layout, "SEL", 9, "4")
        self.beta_edit = self._add_param(layout, "BETA", 10, "1e10")
        group.setLayout(layout)
        parent_layout.addWidget(group)

    def _add_param(self, layout, label, row, default_val, unit=None):
        layout.addWidget(QLabel(label), row, 0)
        edit = QLineEdit(default_val)
        layout.addWidget(edit, row, 1)
        if unit:
            layout.addWidget(QLabel(unit), row, 2)
        return edit

    def load_files(self):
        filenames, _ = QFileDialog.getOpenFileNames(self, "Select CSV Files", "", "CSV Files (*.csv);;All Files (*)")
        if not filenames:
            return
        self.filepaths = filenames
        self.update_filename_display()
        if self.filepaths:
            self.read_file_parameters(self.filepaths[0])

    def update_filename_display(self):
        if not self.filepaths:
            self.filename_display.setText("Display the filename you have selected")
        else:
            fnames = [os.path.basename(f) for f in self.filepaths]
            self.filename_display.setText("; ".join(fnames))

    def preview_data(self):
        if not self.filepaths:
            QMessageBox.warning(self, "Warning", "Please load a file first.")
            return
        
        try:
            params = self.read_file_parameters(self.filepaths[0], show_message=False)
            if not params: return
            
            preview_text = f"File: {os.path.basename(params['filepath'])}\n" \
                           f"Data Shape: {params['data_shape']}\n" \
                           f"Sample Rate: {params['sampling_freq'] / 1e6} MHz\n\n"
            preview_text += "Header Parameters:\n"
            for k, v in params['parameters'].items():
                preview_text += f"  - {k}: {v}\n"
            QMessageBox.information(self, "Data Preview", preview_text)

        except Exception as e:
            QMessageBox.critical(self, "Error", f"Could not preview file.\n{e}")

    def read_file_parameters(self, file_path, show_message=True):
        try:
            with open(file_path, 'r') as f:
                header_lines = [f.readline() for _ in range(10)]
            
            params = {}
            for line in header_lines:
                if ':' in line:
                    parts = line.split(':', 1)
                    key = parts[0].strip()
                    value = parts[1].strip()
                    params[key] = value

            df = pd.read_csv(file_path, skiprows=10)
            
            fs_mhz = 0.1
            fs_str = params.get('Sample Rate', '0.1MHz')
            match = re.search(r'(\d*\.?\d+)', fs_str)
            if match:
                fs_mhz = float(match.group(1))

            self.fs_edit.setText(str(fs_mhz))
            sampling_freq = fs_mhz * 1e6
            
            num_points = len(df)
            t_max_us = num_points / sampling_freq * 1e6
            self.t_end_edit.setText(f"{t_max_us:.2f}")

            if show_message:
                QMessageBox.information(self, "Parameters Loaded", f"Parameters from {os.path.basename(file_path)} loaded.")

            return {
                'filepath': file_path,
                'data_shape': df.shape,
                'parameters': params,
                'sampling_freq': sampling_freq
            }

        except Exception as e:
            if show_message:
                QMessageBox.critical(self, "Error Reading File", f"Could not read parameters from file: {file_path}\nError: {e}")
            return None

    def plot_data(self):
        if not self.filepaths:
            QMessageBox.warning(self, "Warning", "Please load data file(s) first.")
            return
        
        channels_to_plot = [i for i, cb in enumerate(self.channel_checkboxes) if cb.isChecked()]
        if not channels_to_plot:
            channels_to_plot = [0]
            self.channel_checkboxes[0].setChecked(True)
        
        plot_or_save = 'Save' if self.plot_mode3_group.checkedButton().text() == 'Save Figure' else 'Plot'

        self.process_files(channels_to_plot, plot_or_save)
        
    def process_files(self, channels_to_plot, plot_or_save):
        new_filepaths = list(self.filepaths)

        # --- Single Plot Mode ---
        if self.plot_style_single.isChecked():
            # In 'Plot' mode, we process one file at a time, each in a new window/saved file
            if plot_or_save == 'Plot':
                files_to_process = self.filepaths
            else: # Save mode also processes all files
                files_to_process = self.filepaths

            for i, file_path in enumerate(files_to_process):
                t, ADCs, ADCsorg, params, smplfreq, CMname = self.get_file_data(file_path)
                if t is None:
                    QMessageBox.warning(self, "Warning", f"Skipping file due to read error: {os.path.basename(file_path)}")
                    continue
                
                # Create a new figure for each file
                npR, npC = self.get_plot_r_c(len(channels_to_plot))
                fig, axes = plt.subplots(npR, npC, 
                                         figsize=(int(self.figsize_w_combo.currentText()), int(self.figsize_h_combo.currentText())),
                                         squeeze=False)

                self.plot_figure_content(axes, channels_to_plot, t, ADCs, ADCsorg, smplfreq, params, CMname)
                
                header = self.create_header(params, CMname)
                fig.suptitle(header, fontsize=8)
                fig.tight_layout(rect=[0, 0, 1, 0.95])

                if plot_or_save == 'Plot':
                    plt.show()
                else: # Save
                    # Spurious Check
                    spurious_flag = 0
                    if self.spurious_off_radio.isChecked() and ADCsorg is not None and ADCsorg.shape[1] > 0:
                        Pt = ADCsorg[:, 0]
                        PtA = np.abs(Pt)
                        PtAd = PtA[1:] - PtA[:-1]
                        if (len(np.where(PtAd > 100)[0]) > 0 and len(np.where(PtAd < -100)[0]) > 0) or \
                           (len(np.where(PtAd > 100)[0]) + len(np.where(PtAd < -100)[0]) > 3):
                            spurious_flag = 1
                    # Save and Move
                    try:
                        datapath, filename = os.path.split(file_path)
                        fname, _ = os.path.splitext(filename)
                        modulename = CMname[:-2] if len(CMname) > 2 else CMname
                        target_dir = os.path.join(datapath, modulename)
                        if spurious_flag:
                            target_dir = os.path.join(target_dir, "spurious")
                        os.makedirs(target_dir, exist_ok=True)
                        png_path = os.path.join(target_dir, f"{fname}.png")
                        new_csv_path = os.path.join(target_dir, filename)
                        
                        fig.savefig(png_path)
                        plt.close(fig)
                        shutil.move(file_path, new_csv_path)
                        new_filepaths[i] = new_csv_path
                    except Exception as e:
                        QMessageBox.critical(self, "Error", f"Could not save/move file {os.path.basename(file_path)}.\nError: {e}")
                        plt.close(fig)
                        break
            
            if plot_or_save == 'Save':
                self.filepaths = new_filepaths
                self.update_filename_display()
                QMessageBox.information(self, "Success", f"{len(files_to_process)} file(s) processed and saved.")

        # --- Multi Plot Mode ---
        else:
            npR, npC = self.get_plot_r_c(len(channels_to_plot))
            fig, axes = plt.subplots(npR, npC, 
                                     figsize=(int(self.figsize_w_combo.currentText()), int(self.figsize_h_combo.currentText())),
                                     squeeze=False)
            final_header = ""
            final_png_path = ""

            try: # Add try-except around the entire plotting and saving process
                for i, file_path in enumerate(self.filepaths):
                    t, ADCs, ADCsorg, params, smplfreq, CMname = self.get_file_data(file_path)
                    if t is None: continue

                    self.plot_figure_content(axes, channels_to_plot, t, ADCs, ADCsorg, smplfreq, params, CMname, multi_plot_legend=CMname)
                    final_header = self.create_header(params, CMname)

                    if plot_or_save == 'Save':
                        spurious_flag = 0
                        if self.spurious_off_radio.isChecked() and ADCsorg is not None and ADCsorg.shape[1] > 0:
                            Pt = ADCsorg[:, 0]; PtA = np.abs(Pt); PtAd = PtA[1:] - PtA[:-1]
                            if (len(np.where(PtAd > 100)[0]) > 0 and len(np.where(PtAd < -100)[0]) > 0) or \
                               (len(np.where(PtAd > 100)[0]) + len(np.where(PtAd < -100)[0]) > 3):
                                spurious_flag = 1
                        try:
                            datapath, filename = os.path.split(file_path)
                            fname, _ = os.path.splitext(filename)
                            modulename = CMname[:-2] if len(CMname) > 2 else CMname
                            target_dir = os.path.join(datapath, modulename)
                            if spurious_flag: target_dir = os.path.join(target_dir, "spurious")
                            os.makedirs(target_dir, exist_ok=True)
                            final_png_path = os.path.join(target_dir, f"{fname}.png")
                            new_csv_path = os.path.join(target_dir, filename)
                            shutil.move(file_path, new_csv_path)
                            new_filepaths[i] = new_csv_path
                        except Exception as e:
                            QMessageBox.critical(self, "Error", f"Could not move file {filename}.\nError: {e}")
                            break
                
                fig.suptitle(final_header, fontsize=8)
                fig.tight_layout(rect=[0, 0, 1, 0.95])

                # Consolidate legend for multi-plot
                handles_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
                unique_handles_labels = {}
                for handles, labels in handles_labels:
                    for handle, label in zip(handles, labels):
                        if label not in unique_handles_labels:
                            unique_handles_labels[label] = handle
                if unique_handles_labels:
                    fig.legend(unique_handles_labels.values(), unique_handles_labels.keys(), loc='upper right')

                if plot_or_save == 'Plot':
                    plt.show()
                else: # Save
                    if final_png_path:
                        try:
                            fig.savefig(final_png_path)
                            QMessageBox.information(self, "Success", f"Composite plot saved to {final_png_path} and {len(self.filepaths)} file(s) moved.")
                        except Exception as e:
                            QMessageBox.critical(self, "Error", f"Could not save the final plot.\nError: {e}")
                    plt.close(fig)
                    self.filepaths = new_filepaths
                    self.update_filename_display()
            
            except Exception as e:
                QMessageBox.critical(self, "Plotting Error", f"An unexpected error occurred during plotting or saving:\n{e}")
                if 'fig' in locals() and plt.fignum_exists(fig.number):
                    plt.close(fig)


    def plot_figure_content(self, axes, channels_to_plot, t, ADCs, ADCsorg, smplfreq, params, CMname, multi_plot_legend=None):
        font_size = int(self.font_size_combo.currentText())
        line_width = float(self.linewidth_combo.currentText())
        plot_mode = self.plot_mode1_group.checkedButton().text()
        wave_or_fft = self.plot_mode2_group.checkedButton().text()
        log_y = self.plot_style_logy.isChecked()
        time_unit = self.time_unit_combo.currentText()
        smpl_period = 1.0 / smplfreq
        t_scaled, t_unit_label = self._scale_time_axis(t, smpl_period, time_unit)
        
        # Get channel names based on Auto/Manual mode
        if self.plot_mode4_group.checkedButton().text() == "Manual":
            name_list = [name_edit.text() for name_edit in self.channel_names]
        else: # Auto mode
            name_list = ['Pt', 'Ref', 'Pf', 'Pr1', 'pf2', 'pr2', 'pt2', 'Backup',
                         'Err', 'dErr', 'PI_FB', 'PI+FF', 'FF1', 'FF2', 'Mod', 'DAC']


        num_ch = len(channels_to_plot)
        npR, npC = self.get_plot_r_c(num_ch)

        for idx, ch_idx in enumerate(channels_to_plot):
            ch_name = name_list[ch_idx]
            legend_label = f"{multi_plot_legend} - {ch_name}" if multi_plot_legend else ch_name
            
            # This logic mimics the complex indexing of the original tkinter script
            row, col = 0, 0
            if num_ch <= 4: # Simple grid
                row, col = 0, idx
            elif num_ch <= 8: # 4x3 or 4x4 grid
                row, col = (idx // npC) * 2, idx % npC
            elif num_ch <= 12: # 6x4 grid
                row, col = (idx // npC) * 2, idx % npC
            else: # 8x4 grid
                row, col = (idx // npC) * 2, idx % npC

            if row + 1 >= npR: continue # Bounds check for safety

            ax_amp = axes[row, col]
            ax_phase = axes[row + 1, col]

            if wave_or_fft == 'Wave':
                self._plot_wave_separated(ax_amp, ax_phase, ch_idx, t_scaled, ADCs, ADCsorg, plot_mode, legend_label, ch_name, font_size, line_width, t_unit_label, log_y)
            else: # FFT
                self._plot_fft_separated(ax_amp, ax_phase, ch_idx, t, ADCs, smpl_period, legend_label, ch_name, font_size, line_width)
    
    def _scale_time_axis(self, t, smpl_period, time_unit):
        time_unit_scale = {'[us]': 1e6, '[ms]': 1e3, '[s]': 1, '[samples]': 1 / smpl_period}
        t_unit_label = time_unit if time_unit != '[samples]' else '[samples]'
        scale = time_unit_scale.get(time_unit, 1e6) # Default to us
        return t * scale, t_unit_label

    def _plot_wave_separated(self, ax_amp, ax_phase, ch_idx, t_scaled, ADCs, ADCsorg, plot_mode, legend_label, ch_name, font_size, line_width, t_unit_label, log_y):
        if plot_mode == 'A/P':
            ax_amp.plot(t_scaled, ADCs[:, 2 * ch_idx], linewidth=line_width, label=legend_label)
            ax_phase.plot(t_scaled, ADCs[:, 2 * ch_idx + 1], linewidth=line_width, label=legend_label)
            ax_amp.set_ylabel(f'{ch_name} A [a.u.]', fontsize=font_size)
            ax_phase.set_ylabel(f'{ch_name} P [deg]', fontsize=font_size)
        else: # I/Q
            ax_amp.plot(t_scaled, np.real(ADCsorg[:, ch_idx]), linewidth=line_width, label=legend_label)
            ax_phase.plot(t_scaled, np.imag(ADCsorg[:, ch_idx]), linewidth=line_width, label=legend_label)
            ax_amp.set_ylabel(f'I [a.u.]', fontsize=font_size)
            ax_phase.set_ylabel(f'Q [a.u.]', fontsize=font_size)
        
        if log_y: ax_amp.set_yscale('log')
        if self.p_min_edit.text() and self.p_max_edit.text():
            try: ax_phase.set_ylim(float(self.p_min_edit.text()), float(self.p_max_edit.text()))
            except ValueError: pass
        
        ax_amp.legend(prop={'size': font_size-2})
        ax_phase.legend(prop={'size': font_size-2})
        ax_amp.tick_params(axis='x', labelbottom=False) # Hide x-axis labels on top plot
        ax_phase.set_xlabel(f'Time {t_unit_label}', fontsize=font_size)

    def _plot_fft_separated(self, ax_fft_amp, ax_fft_phase, ch_idx, t, ADCs, smpl_period, legend_label, ch_name, font_size, line_width):
        N_fft = len(t)
        if N_fft == 0: return
        
        xf = np.fft.fftfreq(N_fft, d=smpl_period)
        
        # dA/A
        amp_data = ADCs[:, 2 * ch_idx]
        mean_amp = np.mean(amp_data)
        y_amp = (amp_data - mean_amp) / mean_amp if mean_amp != 0 else amp_data - mean_amp
        yf_amp = np.fft.fft(y_amp)
        ax_fft_amp.plot(np.fft.fftshift(xf), 20 * np.log10(np.abs(np.fft.fftshift(yf_amp))), linewidth=line_width, label=legend_label)
        ax_fft_amp.set_ylabel(f'{ch_name} dA/A [dB]', fontsize=font_size)
        
        # dP
        phase_data = ADCs[:, 2 * ch_idx + 1]
        y_phase = phase_data - np.mean(phase_data)
        yf_phase = np.fft.fft(y_phase)
        ax_fft_phase.plot(np.fft.fftshift(xf), np.abs(np.fft.fftshift(yf_phase)), linewidth=line_width, label=legend_label)
        ax_fft_phase.set_ylabel(f'{ch_name} dP [deg]', fontsize=font_size)

        ax_fft_amp.legend(prop={'size': font_size-2})
        ax_fft_phase.legend(prop={'size': font_size-2})
        ax_fft_amp.tick_params(axis='x', labelbottom=False)
        ax_fft_phase.set_xlabel('Frequency [Hz]', fontsize=font_size)

        if self.f_start_edit.text() and self.f_end_edit.text():
            try:
                f_start = float(self.f_start_edit.text())
                f_end = float(self.f_end_edit.text())
                ax_fft_amp.set_xlim(f_start, f_end)
                ax_fft_phase.set_xlim(f_start, f_end)
            except ValueError: pass

    def get_file_data(self, file_path):
        try:
            params = self.read_file_parameters(file_path, show_message=False)
            df = pd.read_csv(file_path, skiprows=10)
            arr1 = df.to_numpy()
            
            r1, c1 = arr1.shape
            ch_num = c1 // 2
            ADCsorg = np.empty([r1, ch_num], dtype=complex)
            for k in range(ch_num):
                ADCsorg[:, k] = arr1[:, 2 * k] * np.exp(1j * arr1[:, 2 * k + 1] * np.pi / 180)
            
            smplfreq = params['sampling_freq']
            t = np.arange(r1) / smplfreq
            
            fname = os.path.basename(file_path)
            match = re.search(r'(CM\d-\d)', fname)
            CMname = match.group(1) if match else "Unknown"
            
            return t, arr1, ADCsorg, params['parameters'], smplfreq, CMname
        except Exception as e:
            QMessageBox.warning(self, "Error", f"Could not get data from file: {file_path}\nError: {e}")
            return None, None, None, None, None, None

    def create_header(self, params, cm_name):
        para_list = [
            params.get('File Name', '').replace('cm_rdy', cm_name),
            f"Sample Rate: {params.get('Sample Rate', 'N/A')}",
            f"Start Time: {params.get('Start Time', 'N/A')}",
            f"Cavity: {params.get('Cavity', 'N/A')}",
            f"V SET: {params.get('V SET', 'N/A')}",
            f"P SET: {params.get('P SET', 'N/A')}",
            f"MODE: {params.get('MODE', 'N/A')}",
            f"FB ON: {params.get('FB ON', 'N/A')}"
        ]
        return ' | '.join(para_list)

    def get_plot_r_c(self, number_of_subplots):
        if number_of_subplots <= 1: return 2, 1
        if number_of_subplots == 2: return 2, 2
        if number_of_subplots == 3: return 2, 3
        if number_of_subplots == 4: return 2, 4
        if number_of_subplots in [5, 6]: return 4, 3
        if number_of_subplots in [7, 8]: return 4, 4
        if number_of_subplots in [9, 10, 11, 12]: return 6, 4
        return 8, 4

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = DAQ_App()
    ex.show()
    sys.exit(app.exec_()) 